#include "stdafx.h"
#include "../stdafx.h" // keep intellisense happy
#include "database.h"
#include "imgui/imgui.h"
#include "serialise/jsonserialise.h"

bool Database::OnInit(App::Application* pApp)
{
	Initialise();
	return true;
}

void Database::Initialise()
{
	Json::RegisterTypes();

	m_pDatabase = Ut::MakeShared(new Json::Database);
	GetDatabase_NC()->SetName("New Database");
	m_uiState.isSaved = true;
	m_gui.Initialise();
}

void Database::OnRender(Render::SurfacePtr pSurface)
{
	Render::SetRenderTarget(pSurface);
	Render::Clear(0x0);

	Imgui::Window window;
	window.width = Render::GetWidth(pSurface);
	window.height = Render::GetHeight(pSurface);

	RunUI(pSurface, window);
}

void Database::OnEvent(const App::Event* pEvent)
{
	m_gui.OnEvent(pEvent);
}

void Database::RunUI(Render::SurfacePtr pSurface, Imgui::Window& window)
{
	Imgui::Frame frame(m_gui, pSurface, &window);

	{
		Imgui::VerticalList layout(m_gui, Imgui::EMPTY|Imgui::BORDERLESS|Imgui::NO_SEPARATION, &window);

		// Menu.
		{
			const char* options[] = {"File", "View"};
			int selected;

			if ((selected = m_gui.DoMenuStrip(options, sizeof(options) / sizeof(char*), m_uiState.menuWindow)) > -1)
			{
				m_uiState.selectedMenu = (UiState::Menu) (selected + 1);
			}
		}

		{
			Imgui::HorizontalList layout(m_gui, Imgui::FIT|Imgui::EXPAND|Imgui::EMPTY|Imgui::BORDERLESS|Imgui::NO_SEPARATION);

			DoListWindow();

			switch (m_uiState.editWindow)
			{
				case UiState::EditWindow::Schema:
					DoSchemaEdit();
					break;
				case UiState::EditWindow::Enum:
					DoEnumEdit();
					break;
				case UiState::EditWindow::Record:
					DoRecordEdit();
					break;
				case UiState::EditWindow::Table:
					DoRecordTable();
					break;
			}
		}

		window.width = m_gui.GetFullWindow().width;
	}

	DoMenus();
}

bool Database::Save()
{
	if (!m_uiState.isSaved)
	{
		for (Json::Record& record : GetDatabase_NC()->GetRecordRange())
			if (GetDatabase()->GetSchema(record.schema))
				GetDatabase_NC()->Schematise(record, *GetDatabase()->GetSchema(record.schema));

		bool success = Json::Serialiser::Save(("content/database/" + GetDatabase()->GetName() + ".json").c_str(), "Database", *GetDatabase());

		if (success)
			m_uiState.isSaved = true;

		success |= GetDatabase()->WriteHeader(("content/database/" + GetDatabase()->GetName() + ".h").c_str());

		return success;
	}

	return true;
}

bool Database::Load()
{
	std::string name = m_pDatabase->GetName();
	m_pDatabase = Ut::MakeShared(new Json::Database);
	bool success = Json::Deserialiser::Load(("content/database/" + name + ".json").c_str(), "Database", *m_pDatabase);
	m_uiState.isSaved = true;
	m_uiState.selected.clear();
	m_uiState.nodeData.clear();

	return success;
}

void Database::DoMenus()
{
	std::vector<const char*> options;
	int selected;

	switch (m_uiState.selectedMenu)
	{
		case UiState::Menu::File:
		{
			const char* options[] = {"New", "Open", "Save", "Save As", "Exit"};
			
			if ((selected = m_gui.DoMenu(options, 0, sizeof(options) / sizeof(char*), 0, &m_uiState.menuWindow)) > -1)
			{
				switch (selected) 
				{
				case 0:
					m_uiState.pendingActions.push_back(UiState::Action::RenameDatabase);
					m_uiState.pendingActions.push_back(UiState::Action::New); 
					if (!m_uiState.isSaved) m_uiState.pendingActions.push_back(UiState::Action::RequestSave);
					break;
				case 1:
					m_uiState.pendingActions.push_back(UiState::Action::Open);
					m_uiState.pendingActions.push_back(UiState::Action::RenameDatabase);
					if (!m_uiState.isSaved) m_uiState.pendingActions.push_back(UiState::Action::RequestSave);
					break;
				case 2:
					Save();
					break;
				case 3:
					m_uiState.pendingActions.push_back(UiState::Action::Save);
					m_uiState.pendingActions.push_back(UiState::Action::RenameDatabase);
					break;
				case 4:
					m_uiState.pendingActions.push_back(UiState::Action::Exit);
					if (!m_uiState.isSaved) m_uiState.pendingActions.push_back(UiState::Action::RequestSave);
					break;
				}

				m_uiState.selectedMenu = UiState::Menu::None;
			}

			break;
		}
		case UiState::Menu::View:
		{
			const char* options[] = {"Records", "Table", "Schemas", "Enums"};
			
			if ((selected = m_gui.DoMenu(options, 0, sizeof(options) / sizeof(char*), 0, &m_uiState.menuWindow)) > -1)
			{
				switch (selected) 
				{
				case 0:
					m_uiState.editWindow = UiState::EditWindow::Record;
					break;
				case 1:
					m_uiState.editWindow = UiState::EditWindow::Table;
					break;
				case 2:
					m_uiState.editWindow = UiState::EditWindow::Schema;
					break;
				case 3:
					m_uiState.editWindow = UiState::EditWindow::Enum;
					break;
				}

				m_uiState.selectedMenu = UiState::Menu::None;
			}

			break;
		}
		case UiState::Menu::FieldEdit:
		{
			const char* options[] = {"Rename", "Delete"};
			
			if ((selected = m_gui.DoMenu(options, 0, sizeof(options) / sizeof(char*), 0, &m_uiState.menuWindow)) > -1)
			{
				switch (selected) 
				{
					case 0:
					{
						m_uiState.pendingActions.push_back(UiState::Action::RenameField);
						break;
					}
					case 1:
					{
						Json::Schema::Object* pObject = GetDatabase_NC()->GetSchema(m_uiState.schemaView);
						m_uiState.pCurrentTypeInfo = pObject->fields.erase(m_uiState.pCurrentTypeInfo);
						break;
					}
				}

				m_uiState.selectedMenu = UiState::Menu::None;
			}

			break;
		}
		case UiState::Menu::FieldType:
		{
			bool isObject = m_uiState.pCurrentTypeInfo->second.basicType == Json::Schema::Type::Object
							|| m_uiState.pCurrentTypeInfo->second.basicType == Json::Schema::Type::Reference;

			bool isEnum = m_uiState.pCurrentTypeInfo->second.basicType == Json::Schema::Type::Enum;

			if ((!isObject && !isEnum) || m_uiState.pCurrentTypeInfo->second.isArray)
			{
				options.push_back("Boolean");
				options.push_back("Int");
				options.push_back("Float");
				options.push_back("String");
			}

			int schemaOffset = (int) options.size();

			if (isObject || m_uiState.pCurrentTypeInfo->second.isArray)
			{
				auto range = GetDatabase()->GetSchemaRange();

				for (auto& pair : range)
				{
					options.push_back(pair.first.c_str());
				}
			}

			int enumOffset = (int) options.size();

			if (isEnum || m_uiState.pCurrentTypeInfo->second.isArray)
			{
				auto range = GetDatabase()->GetEnumRange();

				for (auto& pair : range)
				{
					options.push_back(pair.first.c_str());
				}
			}

			selected = m_gui.DoMenu(options.data(), 0, (int) options.size(), 0, &m_uiState.menuWindow);

			if (selected > -1)
			{
				if (selected < (int) options.size())
				{
					if (selected < schemaOffset)
					{
						m_uiState.pCurrentTypeInfo->second.basicType = Json::Schema::GetBasicType(options[selected]);
						m_uiState.pCurrentTypeInfo->second.typeName = "";
					}
					else if (selected < enumOffset)
					{
						if (!isObject)
							m_uiState.pCurrentTypeInfo->second.basicType = Json::Schema::Type::Object;

						m_uiState.pCurrentTypeInfo->second.typeName = options[selected];
					}
					else
					{
						if (!isEnum)
							m_uiState.pCurrentTypeInfo->second.basicType = Json::Schema::Type::Enum;

						m_uiState.pCurrentTypeInfo->second.typeName = options[selected];
					}
				}

				m_uiState.selectedMenu = UiState::Menu::None;
			}

			break;
		}
		case UiState::Menu::Node:
		{
			bool isRoot = (m_uiState.selected.begin()->pParent == 0);
			bool isFolder = (m_uiState.selected.begin()->pFolder != 0);
			const char* options[] = {"Rename", "Delete", "New Folder", "New Record"};
			uint32 flags [] = {isRoot ? Imgui::GHOSTED : 0, isRoot ? Imgui::GHOSTED : 0, isFolder ? 0 : Imgui::GHOSTED, isFolder ? 0 : Imgui::GHOSTED};
			
			if ((selected = m_gui.DoMenu(options, flags, sizeof(options) / sizeof(char*), Imgui::R_MOUSE, &m_uiState.menuWindow)) > -1)
			{
				switch (selected) 
				{
					case 0:
					{
						m_uiState.pendingActions.push_back(UiState::Action::RenameNode);
						break;
					}
					case 1:
					{
						m_uiState.selected.begin()->pParent->Delete(m_uiState.selected.begin()->name);
						m_uiState.isSaved = false;
						break;
					}
					case 2:
					{
						Json::Folder* pParent = m_uiState.selected.begin()->pFolder;
						Json::Folder* pFolder = pParent->InsertFolder("NewFolder");

						if (pFolder)
						{
							m_uiState.nodeData[*m_uiState.selected.begin()].expanded = true;

							UiState::Node node;
							node.name = "NewFolder";
							node.pParent = pParent;
							node.pFolder = pFolder;
							m_uiState.selected.clear();
							m_uiState.selected.insert(node);
							m_uiState.pendingActions.push_back(UiState::Action::RenameNode);

							m_uiState.isSaved = false;
						}

						break;
					}
					case 3:
					{
						Json::Folder* pParent = m_uiState.selected.begin()->pFolder;
						Json::Record* pRecord = pParent->InsertRecord("newRecord");

						if (pRecord)
						{
							m_uiState.nodeData[*m_uiState.selected.begin()].expanded = true;

							UiState::Node node;
							node.name = "newRecord";
							node.pParent = pParent;
							node.pFolder = 0;
							m_uiState.selected.clear();
							m_uiState.selected.insert(node);
							m_uiState.pendingActions.push_back(UiState::Action::RenameNode);

							m_uiState.isSaved = false;
						}

						break;
					}
				}

				m_uiState.selectedMenu = UiState::Menu::None;
			}

			break;
		}
		case UiState::Menu::SchemaSelect:
		{
			auto range = GetDatabase()->GetSchemaRange();

			options.push_back("Null");

			for (auto& pair : range)
			{
				options.push_back(pair.first.c_str());
			}

			selected = m_gui.DoMenu(options.data(), 0, (int) options.size(), 0, &m_uiState.menuWindow);

			if (selected > -1)
			{
				if (selected < (int) options.size())
				{
					Json::Record* pRecord = m_uiState.selected.begin()->pParent->GetRecord(m_uiState.selected.begin()->name);
					pRecord->schema = options[selected];
				}

				m_uiState.selectedMenu = UiState::Menu::None;
			}

			break;
		}
		case UiState::Menu::EnumSelect:
		{
			for (auto& value : m_uiState.pCurrentEnum->values)
			{
				options.push_back(value.c_str());
			}

			selected = m_gui.DoMenu(options.data(), 0, (int) options.size(), 0, &m_uiState.menuWindow);

			if (selected > -1)
			{
				if (selected < (int) options.size())
				{
					*m_uiState.pCurrentField = options[selected];
				}

				m_uiState.selectedMenu = UiState::Menu::None;
			}

			break;
		}
		case UiState::Menu::SchemaEdit:
		{
			const char* options[] = {"Rename", "Delete"};
			
			if ((selected = m_gui.DoMenu(options, 0, sizeof(options) / sizeof(char*), Imgui::R_MOUSE, &m_uiState.menuWindow)) > -1)
			{
				switch (selected) 
				{
					case 0:
					{
						m_uiState.pendingActions.push_back(UiState::Action::RenameSchema);
						break;
					}
					case 1:
					{
						GetDatabase_NC()->EraseSchema(m_uiState.schemaEdit);
						break;
					}
				}

				m_uiState.selectedMenu = UiState::Menu::None;
			}

			break;
		}
		case UiState::Menu::EnumEdit:
		{
			const char* options[] = {"Rename", "Delete"};
			
			if ((selected = m_gui.DoMenu(options, 0, sizeof(options) / sizeof(char*), Imgui::R_MOUSE, &m_uiState.menuWindow)) > -1)
			{
				switch (selected) 
				{
					case 0:
					{
						m_uiState.pendingActions.push_back(UiState::Action::RenameEnum);
						break;
					}
					case 1:
					{
						GetDatabase_NC()->EraseEnum(m_uiState.enumEdit);
						break;
					}
				}

				m_uiState.selectedMenu = UiState::Menu::None;
			}

			break;
		}
		case UiState::Menu::EnumValueEdit:
		{
			const char* options[] = {"Rename", "Delete"};
			
			if ((selected = m_gui.DoMenu(options, 0, sizeof(options) / sizeof(char*), 0, &m_uiState.menuWindow)) > -1)
			{
				switch (selected) 
				{
					case 0:
					{
						m_uiState.pendingActions.push_back(UiState::Action::RenameEnumValue);
						break;
					}
					case 1:
					{
						Json::Schema::Enum* pEnum = GetDatabase_NC()->GetEnum(m_uiState.enumView);
						pEnum->values.erase(m_uiState.enumValue);
						m_uiState.enumValue = "";
						break;
					}
				}

				m_uiState.selectedMenu = UiState::Menu::None;
			}

			break;
		}
		case UiState::Menu::MemberEdit:
		{
			if (m_uiState.recordPointStack.back().jBase->GetObject())
				options.push_back("Rename");

			options.push_back("Delete");

			selected = m_gui.DoMenu(options.data(), 0, (int) options.size(), 0, &m_uiState.menuWindow);

			if (selected > -1)
			{
				if (selected < (int) options.size())
				{
					if (strcmp(options[selected], "Rename") == 0)
					{
						m_uiState.pendingActions.push_back(UiState::Action::RenameMember);
					}
					else if (strcmp(options[selected], "Delete") == 0)
					{
						m_uiState.recordPointStack.back().jBase->Erase(m_uiState.pCurrentMember);
						m_uiState.isSaved = false;
					}
				}

				m_uiState.selectedMenu = UiState::Menu::None;
			}

			break;
		}
	}

	if (!m_uiState.pendingActions.empty())
	{
		switch(m_uiState.pendingActions.back())
		{
			case UiState::Action::RequestSave:
			{
				if (DoSaveChangesDialog())
					m_uiState.pendingActions.pop_back();

				break;
			}
			case UiState::Action::New:
			{
				m_pDatabase = Ut::MakeShared(new Json::Database);
				GetDatabase_NC()->SetName("New Database");
				m_uiState.pendingActions.pop_back();
				m_uiState.selected.clear();
				m_uiState.nodeData.clear();
				break;
			}
			case UiState::Action::Open:
			{
				Load();
				m_uiState.pendingActions.pop_back();
				break;
			}
			case UiState::Action::Save:
			{
				Save();
				m_uiState.pendingActions.pop_back();
				break;
			}
			case UiState::Action::Exit:
			{
				OnExit();
				m_uiState.pendingActions.pop_back();
				break;
			}
			case UiState::Action::RenameNode:
			{
				std::string newName;

				if (m_gui.DoTextBox(newName, Imgui::FORCE_FOCUS|Imgui::USE_INPUT_XY, &m_uiState.buttonWindow))
				{
					m_uiState.selected.begin()->pParent->Rename(m_uiState.selected.begin()->name, newName);
					m_uiState.isSaved = false;
					m_uiState.pendingActions.pop_back();
				}
			}
		}
	}
}

bool Database::DoSaveChangesDialog()
{
	bool triggered = false;

	Imgui::VerticalList layout(m_gui, Imgui::CENTRE|Imgui::FORCE_FOCUS|Imgui::FLOAT);

	m_gui.DoLabel("Save changes to database?");

	{
		Imgui::HorizontalList layout(m_gui, Imgui::EXPAND|Imgui::BORDERLESS);

		if (m_gui.DoButton("Cancel", Imgui::BACK))
		{
			m_uiState.pendingActions.pop_back();
			triggered = true;
		}
		
		if (m_gui.DoButton("No", Imgui::BACK))
		{
			triggered = true;
		}

		if (m_gui.DoButton("Yes", Imgui::BACK))
		{
			Save();
			triggered = true;
		}
	}

	return triggered;
}

void Database::DoListWindow()
{
	Imgui::VerticalList layout(m_gui, Imgui::EXPAND);

	if (!m_uiState.pendingActions.empty() && m_uiState.pendingActions.back() == UiState::Action::RenameDatabase)
	{
		std::string name;

		if (m_gui.DoTextBox(name, Imgui::FORCE_FOCUS|Imgui::EXPAND))
		{
			GetDatabase_NC()->SetName(name);
			m_uiState.isSaved = false;
			m_uiState.pendingActions.pop_back();
		}
	}
	else
	{
		std::string name = GetDatabase()->GetName();
		if (!m_uiState.isSaved) name += "*";
		m_gui.DoLabel(name.c_str(), Imgui::EXPAND|Imgui::BORDERLESS|Imgui::TITLE);
	}

	if (m_uiState.editWindow == UiState::EditWindow::Schema || m_uiState.editWindow == UiState::EditWindow::Table)
	{
		m_gui.DoLabel("Schemas");
		DoSchemaList();
	}
	else if (m_uiState.editWindow == UiState::EditWindow::Enum)
	{
		m_gui.DoLabel("Enums");
		DoEnumList();
	}
	else
	{
		m_gui.DoLabel("Records");
		DoRecordTree();
	}
}

void Database::DoRecordTree()
{
	Imgui::Tree tree(m_gui, Imgui::FIT|Imgui::EXPAND|Imgui::BORDERLESS);

	DoNode(tree, "Root", &m_pDatabase->GetRoot(), 0);
}

void Database::DoNode(Imgui::Tree& tree, const std::string& name, Json::Folder* pFolder, Json::Folder* pParent)
{
	bool rClick = false;

	UiState::Node node;
	node.name = name;
	node.pParent = pParent;
	node.pFolder = pFolder;

	bool selected = m_uiState.selected.find(node) != m_uiState.selected.end();
	bool isFolder = (pFolder != 0);

	Imgui::Window window;

	if (m_gui.DoTreeNode(name.c_str(), 
						isFolder,
						m_uiState.nodeData[node].expanded, 
						rClick, 
						selected ? Imgui::SELECTED : 0, 
						&window))
	{
		bool multi = App::KeyDown(App::KEY_RCTRL) || App::KeyDown(App::KEY_LCTRL);

		if (!multi)
		{
			m_uiState.selected.clear();
			m_uiState.selected.insert(node);
		}
		else if (selected)
		{
			m_uiState.selected.erase(node);
		}
		else
		{
			m_uiState.selected.insert(node);
		}
	}

	if (rClick)
	{
		m_uiState.selected.clear();
		m_uiState.selected.insert(node);
		m_uiState.selectedMenu = UiState::Menu::Node;
		m_uiState.buttonWindow = window;
		auto mouse = App::GetMouseState();
		m_uiState.menuWindow.x = mouse.x;
		m_uiState.menuWindow.y = mouse.y;
	}

	if (!m_uiState.pendingActions.empty() && m_uiState.pendingActions.back() == UiState::Action::RenameNode && node == *m_uiState.selected.begin())
	{
		m_uiState.buttonWindow = window;
	}

	if (m_uiState.nodeData[node].expanded)
	{
		tree.BeginBranch();

		for (auto& pair : pFolder->GetFolderRange())
			DoNode(tree, pair.first, &pair.second, pFolder);

		for (auto& pair : pFolder->GetRecordRange())
			DoNode(tree, pair.first, 0, pFolder);

		tree.EndBranch();
	}
}

void Database::DoSchemaList()
{
	std::vector<const char*> items;

	for (auto& object : GetDatabase()->GetSchemaRange())
	{
		items.push_back(object.first.c_str());
	}

	int selected;
	bool rClick;
	if ((selected = m_gui.DoListBox(items.data(), 0, (int) items.size(), rClick, Imgui::EXPAND|Imgui::BORDERLESS)) > -1)
	{
		if (rClick)
		{
			m_uiState.schemaEdit = items[selected];
			m_uiState.selectedMenu = UiState::Menu::SchemaEdit;
			auto mouse = App::GetMouseState();
			m_uiState.menuWindow.x = mouse.x;
			m_uiState.menuWindow.y = mouse.y;
		}
		else
		{
			m_uiState.schemaView = items[selected];
		}
	}

	if (!m_uiState.pendingActions.empty() && m_uiState.pendingActions.back() == UiState::Action::RenameSchema)
	{
		std::string name;
		if (m_gui.DoTextBox(name, Imgui::FORCE_FOCUS))
		{
			if (name != "")
			{
				GetDatabase_NC()->RenameSchema(m_uiState.schemaEdit, name);
				m_uiState.pendingActions.pop_back();
			}
		}
	}

	static bool doAddSchema = false;

	if (doAddSchema)
	{
		std::string name;
		if (m_gui.DoTextBox(name, Imgui::FORCE_FOCUS))
		{
			if (name != "")
			{
				m_pDatabase->InsertSchema(name);
				m_uiState.isSaved = false;
				doAddSchema = false;
			}
		}
	}

	if (m_gui.DoButton("New Schema"))
		doAddSchema = true;
}

void Database::DoEnumList()
{
	std::vector<const char*> items;

	for (auto& object : m_pDatabase->GetEnumRange())
	{
		items.push_back(object.first.c_str());
	}

	int selected;
	bool rClick;
	if ((selected = m_gui.DoListBox(items.data(), 0, (int) items.size(), rClick, Imgui::EXPAND|Imgui::BORDERLESS)) > -1)
	{
		if (rClick)
		{
			m_uiState.enumEdit = items[selected];
			m_uiState.selectedMenu = UiState::Menu::EnumEdit;
			auto mouse = App::GetMouseState();
			m_uiState.menuWindow.x = mouse.x;
			m_uiState.menuWindow.y = mouse.y;
		}
		else
		{
			m_uiState.enumView = items[selected];
		}
	}

	if (!m_uiState.pendingActions.empty() && m_uiState.pendingActions.back() == UiState::Action::RenameEnum)
	{
		std::string name;
		if (m_gui.DoTextBox(name, Imgui::FORCE_FOCUS))
		{
			if (name != "")
			{
				GetDatabase_NC()->RenameEnum(m_uiState.enumEdit, name);
				m_uiState.pendingActions.pop_back();
			}
		}
	}

	static bool doAddEnum = false;

	if (doAddEnum)
	{
		std::string name;

		if (m_gui.DoTextBox(name, Imgui::FORCE_FOCUS))
		{
			if (name != "")
			{
				GetDatabase_NC()->InsertEnum(name);
				m_uiState.isSaved = false;
				doAddEnum = false;
			}
		}
	}

	if (m_gui.DoButton("New Enum"))
		doAddEnum = true;
}

void Database::DoEnumEdit()
{
	Imgui::VerticalList layout(m_gui, Imgui::FIT|Imgui::EXPAND);

	m_gui.DoLabel("Enum", Imgui::EXPAND|Imgui::BORDERLESS|Imgui::TITLE);

	if (Json::Schema::Enum* pEnum = m_pDatabase->GetEnum(m_uiState.enumView))
	{
		Imgui::VerticalList layout(m_gui, Imgui::FIT|Imgui::EXPAND);

		m_gui.DoLabel((m_uiState.enumView).c_str());

		for (auto& value : pEnum->values)
		{
			Imgui::HorizontalList layout(m_gui, Imgui::EXPAND|Imgui::BORDERLESS);

			Imgui::Window menuWindow;
			if (m_gui.DoButton(">", Imgui::ON_MOUSE_DOWN|Imgui::BORDERLESS, &menuWindow))
			{
				m_uiState.selectedMenu = UiState::Menu::EnumValueEdit;
				m_uiState.menuWindow = menuWindow;
				m_uiState.enumValue = value;
			}

			if (!m_uiState.pendingActions.empty() && m_uiState.pendingActions.back() == UiState::Action::RenameEnumValue && m_uiState.enumValue == value)
			{
				std::string name;
				if (m_gui.DoTextBox(name, Imgui::FORCE_FOCUS))
				{
					if (name != "")
					{
						m_uiState.pendingActions.pop_back();
						pEnum->RenameValue(value, name, GetDatabase_NC()->IncrementRevision());
						break;
					}
				}
			}
			else
			{
				m_gui.DoLabel(value.c_str());
			}
		}

		static bool doAddValue = false;

		if (doAddValue)
		{
			std::string name;

			if (m_gui.DoTextBox(name, Imgui::FORCE_FOCUS))
			{
				if (name != "")
				{
					pEnum->values.insert(name);
					m_uiState.isSaved = false;
					doAddValue = false;
				}
			}
		}

		if (m_gui.DoButton("+"))
			doAddValue = true;
	}
}

void Database::DoSchemaEdit()
{
	Imgui::VerticalList layout(m_gui, Imgui::FIT|Imgui::EXPAND);

	m_gui.DoLabel("Schema", Imgui::EXPAND|Imgui::BORDERLESS|Imgui::TITLE);

	if (Json::Schema::Object* pSchema = m_pDatabase->GetSchema(m_uiState.schemaView))
	{
		Imgui::VerticalList layout(m_gui, Imgui::EXPAND);

		m_gui.DoLabel(m_uiState.schemaView.c_str());

		for (auto it = pSchema->fields.begin(); it != pSchema->fields.end(); ++it)
		{
			Imgui::HorizontalList layout(m_gui, Imgui::EXPAND|Imgui::BORDERLESS);

			Imgui::Window menuWindow;
			if (m_gui.DoButton(">", Imgui::ON_MOUSE_DOWN|Imgui::BORDERLESS, &menuWindow))
			{
				m_uiState.selectedMenu = UiState::Menu::FieldEdit;
				m_uiState.menuWindow = menuWindow;
				m_uiState.pCurrentTypeInfo = it;
			}
			
			if (m_gui.DoButton(Json::Schema::GetTypeName(it->second).c_str(), Imgui::ON_MOUSE_DOWN|Imgui::BORDERLESS, &menuWindow))
			{
				m_uiState.selectedMenu = UiState::Menu::FieldType;
				m_uiState.menuWindow = menuWindow;
				m_uiState.pCurrentTypeInfo = it;
			}

			if (!m_uiState.pendingActions.empty() 
				&& m_uiState.pendingActions.back() == UiState::Action::RenameField 
				&& m_uiState.pCurrentTypeInfo == it)
			{
				std::string name;

				if (m_gui.DoTextBox(name, Imgui::FORCE_FOCUS|Imgui::EXPAND))
				{
					pSchema->RenameField(it->first, name, GetDatabase_NC()->IncrementRevision());
					m_uiState.isSaved = false;
					m_uiState.pendingActions.pop_back();
					break;
				}
			}
			else
			{
				m_gui.DoLabel(it->first.c_str());
			}

			if (it->second.basicType == Json::Schema::Type::Object)
				m_gui.DoLabel("{}");

			if (it->second.isArray)
				m_gui.DoLabel("[]");

			if (it->second.basicType == Json::Schema::Type::Reference)
				m_gui.DoLabel("&");
		}

		static int newElementType = 0;

		if (newElementType != 0)
		{
			std::string name;

			if (m_gui.DoTextBox(name, Imgui::FORCE_FOCUS))
			{
				if (name != "" && !HasMember(*pSchema, name.c_str()))
				{
					if (newElementType == 1)
					{
						auto pair = pSchema->fields.emplace(name, Json::Schema::FieldInfo(Json::Schema::Type::Object));
						pair.first->second.typeName = "Null";
					}
					else if (newElementType == 2)
					{
						auto pair = pSchema->fields.emplace(name, Json::Schema::FieldInfo(Json::Schema::Type::Null));
						pair.first->second.isArray = true;
					}
					else if (newElementType == 3)
					{
						pSchema->fields.emplace(name, Json::Schema::FieldInfo(Json::Schema::Type::Null));
					}
					else if (newElementType == 4)
					{
						auto pair = pSchema->fields.emplace(name, Json::Schema::FieldInfo(Json::Schema::Type::Reference));
						pair.first->second.typeName = "Null";
					}
					else if (newElementType == 5)
					{
						auto pair = pSchema->fields.emplace(name, Json::Schema::FieldInfo(Json::Schema::Type::Enum));
						pair.first->second.typeName = "Null";
					}

					m_uiState.isSaved = false;
					newElementType = 0;
				}
			}
		}

		{
			Imgui::HorizontalList layout(m_gui, Imgui::EXPAND|Imgui::BORDERLESS);

			m_gui.DoLabel("New");

			if (m_gui.DoButton("{}"))
				newElementType = 1;

			if (m_gui.DoButton("[]"))
				newElementType = 2;

			if (m_gui.DoButton("&"))
				newElementType = 4;

			if (m_gui.DoButton("Basic"))
				newElementType = 3;

			if (m_gui.DoButton("Enum"))
				newElementType = 5;
		}
	}
}

void GetRecordsMatchingSchema(const std::string schema, Json::Folder* pFolder, std::vector<std::pair<std::string, Json::Record*>>& records)
{
	for (auto& record : pFolder->GetRecordRange())
	{
		if (record.second.schema == schema)
			records.emplace_back(record.first, &record.second);
	}

	for (auto& folder : pFolder->GetFolderRange())
	{
		GetRecordsMatchingSchema(schema, &folder.second, records);
	}
}

void Database::DoRecordTable()
{
	Imgui::VerticalList layout(m_gui, Imgui::FIT|Imgui::EXPAND);

	m_gui.DoLabel("Record Table", Imgui::EXPAND|Imgui::BORDERLESS|Imgui::TITLE);

	const Json::Schema::Object* pSchema = GetDatabase()->GetSchema(m_uiState.schemaView);

	std::vector<std::pair<std::string, Json::Record*>> records;
	GetRecordsMatchingSchema(m_uiState.schemaView, &m_pDatabase->GetRoot(), records);

	if (pSchema)
	{
		Imgui::Table table(m_gui, Imgui::EXPAND|Imgui::BORDERLESS);

		{
			table.BeginRow();

			m_gui.DoLabel(m_uiState.schemaView.c_str());

			for (auto& pair : pSchema->fields)
			{
				m_gui.DoLabel(pair.first.c_str());
			}

			table.EndRow();
		}

		for (auto& pair : records)
		{
			m_pDatabase->Schematise(*pair.second, *pSchema);

			table.BeginRow();

			m_gui.DoLabel(pair.first.c_str());

			for (auto& field : pSchema->fields)
			{
				const Json::Schema::FieldInfo& fieldInfo(field.second);
				Json::JBase* pJBase = pair.second->pObject->Get(field.first.c_str());

				if (fieldInfo.basicType == Json::Schema::Type::Enum)
				{
					std::string value = *pJBase->GetString();
					value.erase(remove_if(value.begin(), value.end(), [](const char& c) {return c == '\"';}), value.end());

					Imgui::Window menuWindow;
					if (m_gui.DoButton(value.c_str(), Imgui::BACK|Imgui::ON_MOUSE_DOWN|Imgui::BORDERLESS, &menuWindow))
					{
						m_uiState.selectedMenu = UiState::Menu::EnumSelect;
						m_uiState.menuWindow = menuWindow;
						m_uiState.pCurrentField = pJBase->GetString();
						m_uiState.pCurrentEnum = m_pDatabase->GetEnum(fieldInfo.typeName);
					}
				}
				else if (pJBase->GetString())
				{
					std::string value = *pJBase->GetString();
					value.erase(remove_if(value.begin(), value.end(), [](const char& c) {return c == '\"';}), value.end());

					if (fieldInfo.basicType == Json::Schema::Type::Boolean)
					{
						if (m_gui.DoButton(value.c_str(), Imgui::BACK))
						{
							if (value == "true") *pJBase->GetString() = "false";
							else *pJBase->GetString() = "true";
						}
					}
					else if (m_gui.DoTextBox(value, Imgui::BACK))
					{
						*pJBase->GetString() = value;
						m_uiState.isSaved = false;
					}
				}
				else
				{
					m_gui.DoLabel("");
				}
			}

			table.EndRow();
		}
	}
}

void Database::DoRecordEdit()
{
	Imgui::VerticalList layout(m_gui, Imgui::FIT|Imgui::EXPAND);

	m_gui.DoLabel("Record", Imgui::EXPAND|Imgui::BORDERLESS|Imgui::TITLE);

	if (m_uiState.selected.size() == 1 && m_uiState.selected.begin()->pFolder == 0)
	{
		if (Json::Record* pRecord = m_uiState.selected.begin()->pParent->GetRecord(m_uiState.selected.begin()->name))
		{
			const Json::Schema::Object* pSchema = GetDatabase()->GetSchema(pRecord->schema);

			{
				Imgui::HorizontalList layout(m_gui, Imgui::EXPAND|Imgui::BORDERLESS);

				m_gui.DoLabel("Type:");

				Imgui::Window menuWindow;
				if (m_gui.DoButton(pRecord->schema.c_str(), Imgui::ON_MOUSE_DOWN|Imgui::BORDERLESS, &menuWindow))
				{
					m_uiState.selectedMenu = UiState::Menu::SchemaSelect;
					m_uiState.menuWindow = menuWindow;
				}
			}

			{
				if (pRecord->pObject == 0)
					pRecord->pObject = Ut::MakeShared(new Json::JObject);

				if (!m_uiState.recordPointStack.empty() && m_uiState.recordPointStack.front().jBase != pRecord->pObject)
					m_uiState.recordPointStack.clear();

				if (m_uiState.recordPointStack.empty())
					m_uiState.recordPointStack.emplace_back();

				m_uiState.recordPointStack.front().jBase = pRecord->pObject;
				m_uiState.recordPointStack.front().name = m_uiState.selected.begin()->name;
				m_uiState.recordPointStack.front().pSchema = pSchema; 

				if (pSchema)
					m_pDatabase->Schematise(*pRecord, *pSchema);

				DoJObjectUI(m_uiState.recordPointStack.back().jBase, m_uiState.recordPointStack.back().pSchema);
			}
		}
	}
}

void Database::DoReferenceButton(const std::string& value)
{
	if (const Json::Record* pRecord = GetDatabase()->FindRecord(value.c_str()))
	{
		if (m_gui.DoButton("&", Imgui::BACK))
		{
			UiState::Node node;
			std::string::size_type pos = value.find_last_of("/");
			node.name = value.substr(pos + 1, value.size() - pos - 1);
			node.pParent = m_pDatabase->FindFolder(value.substr(0, pos).c_str());
			node.pFolder = 0;
			m_uiState.selected.clear();
			m_uiState.selected.insert(node);
		}
	}
	else
	{
		m_gui.DoButton("&", Imgui::BACK|Imgui::GHOSTED);
	}
}

void Database::DoJObjectUI(Json::JBase* jBase, const Json::Schema::Object* pSchema)
{
	Imgui::VerticalList layout(m_gui, 0);

	{
		Imgui::HorizontalList layout(m_gui, Imgui::EXPAND|Imgui::BORDERLESS);

		if (m_uiState.recordPointStack.size() > 1 && m_gui.DoButton("Up"))
		{
			m_uiState.recordPointStack.pop_back();
		}
		else
		{
			m_gui.DoLabel(m_uiState.recordPointStack.back().name.c_str());
		}
	}

	Json::JObject* jObject = jBase->GetObject();
	Json::JArray* jArray = jBase->GetArray();

	static int newElementType = 0;

	// Member list.
	{
		if (jObject)
		{
			Imgui::VerticalList layout(m_gui, Imgui::EXPAND);

			Json::JObject::Range range(jObject->GetMembers());

			for (Json::JObject::Range::Iterator it = range.begin(); it != range.end(); ++it)
			{
				const Json::Schema::FieldInfo* pField = 0;

				if (pSchema)
				{
					auto field = pSchema->fields.find(it->first);
					
					if (field == pSchema->fields.end()) 
						pField = 0;
					else 
						pField = &field->second;
				}

				Imgui::HorizontalList layout(m_gui, Imgui::EXPAND|Imgui::BORDERLESS);

				if (!pSchema)
				{
					static Imgui::Window menuWindow;
					if (m_gui.DoButton(">", Imgui::ON_MOUSE_DOWN|Imgui::BORDERLESS, &menuWindow))
					{
						m_uiState.selectedMenu = UiState::Menu::MemberEdit;
						m_uiState.menuWindow = menuWindow;
						m_uiState.menuWindow.y += menuWindow.height;
						m_uiState.pCurrentMember = it->second;
					}
				}

				std::string name = it->first;

				if (!m_uiState.pendingActions.empty() && m_uiState.pendingActions.back() == UiState::Action::RenameMember)
				{
					if (m_gui.DoTextBox(name, Imgui::FORCE_FOCUS))
					{
						if (name != "")
						{
							Json::JBase* member = it->second->DeepCopy();
							jObject->Erase(it->second);
							jObject->Insert(name, member);
							m_uiState.pendingActions.pop_back();
							break;
						}
					}
				}
				else
				{
					m_gui.DoLabel(name.c_str());
				}

				if (it->second->GetArray())
				{
					if (m_gui.DoButton("[]", Imgui::BACK))
					{
						m_uiState.recordPointStack.emplace_back(it->second, nullptr, name);
							
						if (pField)
						{
							if (pField->basicType == Json::Schema::Type::Object || pField->basicType == Json::Schema::Type::Reference)
								m_uiState.recordPointStack.back().pSchema = m_pDatabase->GetSchema(pField->typeName);
							else
								m_uiState.recordPointStack.back().pSchema = pSchema;
						}
					}
				}
				else if (it->second->GetObject())
				{
					if (m_gui.DoButton("{}", Imgui::BACK))
					{
						m_uiState.recordPointStack.emplace_back(it->second, nullptr, name);

						if (pField)
							m_uiState.recordPointStack.back().pSchema = m_pDatabase->GetSchema(pField->typeName);
					}
				}
				else if (pField && pField->basicType == Json::Schema::Type::Enum)
				{
					std::string value = *it->second->GetString();
					value.erase(remove_if(value.begin(), value.end(), [](const char& c) {return c == '\"';}), value.end());

					Imgui::Window menuWindow;
					if (m_gui.DoButton(value.c_str(), Imgui::BACK|Imgui::ON_MOUSE_DOWN|Imgui::BORDERLESS, &menuWindow))
					{
						m_uiState.selectedMenu = UiState::Menu::EnumSelect;
						m_uiState.menuWindow = menuWindow;
						m_uiState.pCurrentField = it->second->GetString();
						m_uiState.pCurrentEnum = m_pDatabase->GetEnum(pField->typeName);
					}
				}
				else
				{
					std::string value = *it->second->GetString();
					value.erase(remove_if(value.begin(), value.end(), [](const char& c) {return c == '\"';}), value.end());

					if ((pField && pField->basicType == Json::Schema::Type::Reference)
						|| (!pField && value.substr(0, 5) == "Root/"))
						DoReferenceButton(value);

					if (pField && pField->basicType == Json::Schema::Type::Boolean)
					{
						if (m_gui.DoButton(value.c_str(), Imgui::BACK))
						{
							if (value == "true") *it->second->GetString() = "false";
							else *it->second->GetString() = "true";
						}
					}
					else
					{
						if (m_gui.DoTextBox(value, Imgui::BACK))
						{
							*it->second->GetString() = value;
							m_uiState.isSaved = false;
						}
					}
				}
			}
		}
		else if (jArray)
		{
			Imgui::HorizontalList layout(m_gui, Imgui::EXPAND);

			{
				static int listOffset = 0;
				static int scrollHeight = 0;
				static int visibleHeight = 0;

				if (scrollHeight > visibleHeight)
				{
					m_gui.DoVerticalScrollBar(listOffset, visibleHeight / (float) scrollHeight, Imgui::BACK);
				}
				else
				{
					listOffset = 0;
				}

				Imgui::Window window;
				window.offsetY = -listOffset;
				Imgui::VerticalList layout(m_gui, Imgui::EXPAND|Imgui::USE_CACHED_SIZE, &window);
				visibleHeight = m_gui.GetVisibleWindow().height;
				scrollHeight = m_gui.GetFullWindow().height;

				for (int i = 0; i != jArray->Size(); ++i)
				{
					Imgui::HorizontalList layout(m_gui, Imgui::EXPAND|Imgui::BORDERLESS);

					static Imgui::Window menuWindow;
					if (m_gui.DoButton(">", Imgui::ON_MOUSE_DOWN|Imgui::BORDERLESS, &menuWindow))
					{
						m_uiState.selectedMenu = UiState::Menu::MemberEdit;
						m_uiState.menuWindow = menuWindow;
						m_uiState.menuWindow.y += menuWindow.height;
						m_uiState.pCurrentMember = jArray->Get(i);
					}

					std::stringstream ss;
					ss << i;
					std::string name = ss.str();

					m_gui.DoLabel(ss.str().c_str());

					if (jArray->GetObject(i))
					{
						if (m_gui.DoButton("{}", Imgui::BACK))
						{
							m_uiState.recordPointStack.emplace_back(jArray->Get(i), pSchema, name);
						}
					}
					else if (jArray->GetArray(i))
					{
						if (m_gui.DoButton("[]", Imgui::BACK))
						{
							m_uiState.recordPointStack.emplace_back(jArray->Get(i), nullptr, name); 
						}
					}
					else if (jArray->Get(i)->GetString())
					{
						std::string value = *jArray->Get(i)->GetString();
						value.erase(remove_if(value.begin(), value.end(), [](const char& c) {return c == '\"';}), value.end());

						if (value.substr(0, 5) == "Root/")
							DoReferenceButton(value);

						if (m_gui.DoTextBox(value, Imgui::BACK))
						{
							*jArray->Get(i)->GetString() = value;
						}
					}
				}

				if (pSchema)
				{
					if (m_gui.DoButton("+"))
					{
						jArray->PushBack(new Json::JBase());
						m_uiState.isSaved = false;
					}
				}
			}
		}

		if (newElementType)
			if (DoNewElement(jBase, newElementType))
				newElementType = 0;
	}

	if (!pSchema)
	{
		Imgui::HorizontalList layout(m_gui, Imgui::EXPAND|Imgui::BORDERLESS);

		m_gui.DoLabel("New");

		if (m_gui.DoButton("{}"))
			newElementType = 1;

		if (m_gui.DoButton("[]"))
			newElementType = 2;

		if (m_gui.DoButton("''"))
			newElementType = 3;

		if (m_gui.DoButton("&"))
			newElementType = 4;
	}
}

bool Database::DoNewElement(Json::JBase* jBase, int newElementType)
{
	Json::JObject* jObject = jBase->GetObject();
	Json::JArray* jArray = jBase->GetArray();

	if (jObject)
	{
		Imgui::Window textWindow;
		std::string name;

		if (m_gui.DoTextBox(name, Imgui::FORCE_FOCUS, &textWindow))
		{
			if (newElementType == 1)
				jObject->Insert(name, new Json::JObject());
			else if (newElementType == 2)
				jObject->Insert(name, new Json::JArray());
			else if (newElementType == 3)
				jObject->Insert(name, std::string());
			else if (newElementType == 4)
				jObject->Insert(name, std::string("Root/"));

			return true;
		}
	}
	else if (jArray)
	{
		if (newElementType == 1)
			jArray->PushBack(new Json::JObject());
		else if (newElementType == 2)
			jArray->PushBack(new Json::JArray());
		else if (newElementType == 3)
			jArray->PushBack(std::string());
		else if (newElementType == 4)
			jArray->PushBack(std::string("Root/"));

		return true;
	}

	return false;
}


